Copy Constructors
The copy constructor is the third and final type of constructor. As the name implies, the copy constructor takes another instance of the same class type to copy data from. There are three primary situations where the copy constructor is called: using another instance of the class as an initialization parameter, when an object is passed to a function by value (not by reference), and when an object is returned from a function by value.
myClass c; // Calls the copy constructor with c as the source. myClass x(c);
// Because ‘p’ is passed by value, the copy constructor of myClass will be called to copy your // actual parameter into ‘p’ myClass func(myClass p) { // The copy constructor will be called again here, as the program creates a copy of ‘p’ // to return to the calling fuction. return p; }
So, why is the copy constructor useful? The compiler automatically generates a copy constructor if you don't make your own, so why should you be worried? Well, auto-generated copy constructors perform what is called a shallow copy. This is equivalent to simply assigning each member from the source to the copy. For example, if one of those members is a pointer to dynamic memory, the copy will also point to that same memory. This shared memory might be what you want, but more likely not: the copy should have allocated its own memory. Copying that memory, or in general logically copying each set of data is called a deep copy, and requires you to implement your own copy constructor.
Because the copy constructor is called when you pass an object by value, and the copy constructor takes a parameter of your class type, if you pass that parameter by value, you’ll create an infinite loop of calling the copy constructor. Hence, you must pass the parameter by reference. And because your copy constructor shouldn’t change anything about the source object, a constant reference. (This is little out of order, see section constant parameters.)
Finally, remember that the copy constructor is still a constructor. You still want to do necessary initialization of your data members, memory allocation, etc...then copy the values from your “source” object.
class myClass { public: myClass(); // Copy constructor myClass(const myClass& src); private: char* str; }; myClass::myClass() { str = new char[50]; } myClass::myClass(const myClass& src) { // Here, we must allocate our own string and copy str = new char[50]; strcpy(str,src.str); }
Friendship
Friendship is extremely simple: it allows non-member functions and classes to have access to private data members. You can selectively specify what functions or classes can access private data within your class definition. To do this, simply add the keyword “friend” before either a function prototype or class name. This allows that function or class to access private data members of instances of the specified type—without being members of that class.
For example, if a function is passed a friend class object, it can access the private members of that instance. And because friend functions are not member functions, they are not called using an instance of the object and the dot operator. Instead, they are called just like any other function you’ve used in the past. Hence, there is no calling object, and to access data from an instance, that instance must be passed as a parameter.
Friendship is particularly useful when you are forced to implement a function as a non-member that needs private access, or when you want to make a class only accessible by another, or a specific function.
class card { public: // Member functions void setRank(const char* r); char* getRank() const; private: // Data members char* rank; // Friends friend void print(const card& c); friend class deck; };
Here, friendship allows the print function and the deck class access to the normally private rank data member.
void print(const card& c) { cout << “rank: “ << c.rank; }
print() can access the rank member of the card.
int main() { card c; print(c); }
Finally, you can see that calling the friend function is no different than calling any other function.
Another interesting feature of friendship is that it gives you the option to allow only a specific class or function the ability to create instances of another class. Remember when I said your constructors should always be public? Well, if you make all constructors private—and give friendship—a friend class can create instances of your private class, as it can access private members, but no where else can.
class card { // Default access modifier is private card(); int value; friend class deck; }; class deck { public: deck(); card* makeCard() const; }; card* deck::makeCard() const { // This calls card's private constructor. // Only deck can do this, as it has friendship return new card; }
Static Members
Remember static variables? Well, you can do something similar with class members. The most basic use is simply having a static data member. A static data member will be globally shared between all instances of the class. Each instance can update it for everyone. For example, all instances could have a unique ID, and a static member to keep track of the used IDs.
Furthermore, a static data member (if public) can be accessed by other code, without an instance of the class. However, if a static member is public, it should almost always be constant (see next section). Public, static, constant members are useful to provide the rest of your program with values related to that class. For example, you could have the dimensions of a game board class be public, static, and constant.
To access a (public) static member, use the scope resolution operator (::). This works just like the dot operator, except that instead of an instance, you use the operator on the name of the class.
class deck { public: // This can be accessed anywhere, without an instance of deck const static int SIZE = 52; deck(); // ... private: // This is shared between all instances of deck static int maxID = 0; // This is not int ID; }; deck::deck() { ID = maxID; maxID++; } int main() { // 52 int cards = deck::SIZE; // Will have ID: 0 deck d0; // Will have ID: 1 deck d1; };
Member functions can also be static. However, it doesn't exactly make sense to share a private function between instances—they all have the same logic anyway. Nevertheless, public, static member functions can be useful. Like with data members, a public, static member function can be accessed without an instance of the class. This means that the function does not have a calling object, and consequently cannot access non-static class data members. Public, static member functions are useful for creating "helper" classes which are essentially collections of functions. Additionally, because static functions can access static members, the can be used to store functional state.
class mathHelper { public: static double PI = 3.14159; static double sin(double val); static double cos(double val); // ... private: // Static functions can access this static int tries; }; int main() { // See that we never create an instance of mathHelper double r1 = mathHelper::sin(5.37); double r2 = mathHelper::cos(4.345); double p = mathHelper::PI; };
Constant Variables
Let's go on a bit of a tangent here and talk about constants. The most obvious use of the constant keyword is to define variables whose values cannot change. It is often not incredibly useful to have constant variables in your main code, but a constant can be used to make sure a value that should not change doesn’t change—an error is thrown at an attempt. Additionally, constants can be used to define “global constants.” These are simply global values that can be used by your entire program, but cannot be changed. Hence, they do not suffer the same issues as global (mutable) variables. Global constants are usually named in all caps. You can make any variable constant, no matter the type. You can have a constant integer, character, student, card, or whatever you want.
const int NUM_CARDS = 52;
Global constant defining the size of a deck.
const char* welcome = “Welcome to this program”;
Creates a welcome text string that cannot be changed. If you try to change it later in your program, you will get an error.
Constant Parameters
Another case in which you've seen the constant keyword is in the definition of a function’s parameters. This use case is very similar to creating constant variables: it denotes that the value of the parameter cannot be changed within the function. However, if you pass a variable by value, the function is working with a copy anyway, so this isn't super useful. However, suppose you pass a reference to a function. Now, the function could have side effects: it can change the referenced data. To get around this, we may pass the parameter as a constant reference. Here, you can get the speed benefit of passing by reference, but your function will not be able to change the referenced value. Note that the function may still reassign what the parameter is (locally) referencing.
Hence, it is conventional to pass classes (if not by pointer) by constant reference. Additionally, this allows other code to pass a constant variable as the actual parameter. A constant variable can always be passed by value—there's no way it can be changed in the calling function—but it cannot be passed by reference. This makes sense; the function should not be able to change an elsewhere constant value. However, by passing by constant reference, constant variables are allowed again.
// This function takes a constant reference to a string class. It will not be able to change the value of the string. void func(const string& str); int main() { string h = "hello"; const string w = "world"; // Both of these calls work func(h); func(w); }
Lastly, remember that when you pass a constant pointer, the function cannot change where the pointer points, but it can still change the data that the parameter points to. Be wary of this.
void func2(const char* str);
This function takes a constant character pointer. This means the function will not change where the pointer points, but it can still change the actual data. Additionally, you must use a constant character pointer to pass a string literal (i.e. “hello”) without generating a compiler warning.
Constant Returns
You will probably never use constant returns, as they are essentially obsolete and were never incredibly useful anyhow. Basically, marking a returned value as constant prevents the function from being called on a temporary object. Am example of a temporary object could be the result of an addition, before its value is captured elsewhere. A constant return does not cause the value to become constant in the calling function, so you will most likely never see it do anything at all.
const card deck::shuffle() { return x; }
This function returns a constant card.
Constant Member Functions
You're probably wondering why we've gone on a "const" tangent while learning about classes. This is because member functions of classes can be marked as constant. This means that the function will never change any of the data members of the class. For example, a print function likely should print out values of an object, not changing any of them. Hence, the print function should be constant.
So, why is this useful? Two reasons: first, it is useful simply as a documentation feature, as it marks the function as not changing any data. Second, if you create a constant variable of a class type, you’ll notice you get an error if you try to call any of the member functions. You can only call constant member functions from a constant instance of your object. Of course, this is because the program knows that the function will not change the data of the object. The syntax to mark a member function as constant is simply to add the “const” keyword after your member function prototype, but before the semicolon. In the implementation, add "const" before your opening curly bracket.
class card { public: void setRank(const string& r); // Not a constant member, will change "rank" void print() const; // Constant member private: string rank; };
Given this class, later in your program, if you declare a constant card...
const card c;
You can not call the set function.
c.setRank(“king”); // Error
But you can still call the print function.
c.print(); // Good